<template>
  <div class="cultivation-app">
    <McLayout class="container">
      
      <!-- 头部：游戏标题与核心状态显示 -->
      <McHeader :title="appTitle" :logoImg="logoUrl">
        <template #operationArea>
          <div class="operations">
              <img v-if="!logoError" class="app-logo" :src="logoUrl" alt="logo" @error="onLogoError" @load="onLogoLoad" />
              <span v-else class="icon-emoji" aria-hidden>🏆</span>
              <span class="status-text">境界: {{ characterState.level }}</span>
              <span class="status-text ml-4">灵力: {{ characterState.spirit }}/{{ characterState.maxSpirit }}</span>
              <button class="mini-btn" @click="newGame">重新悟道</button>
          </div>
        </template>
      </McHeader>

      <!-- 消息显示区域：叙事引擎终端（主内容 + 右侧状态栏） -->
      <McLayoutContent class="content-container">
        <div class="content-main">
          <div class="messages-wrap">
            <template v-for="(msg, idx) in messages" :key="idx">
              <McBubble 
                :align="msg.from === 'user' ? 'right' : 'left'" 
                :loading="msg.loading"
              >
                <div class="bubble-inner">
                  <img v-if="(msg.avatarConfig && msg.avatarConfig.imgSrc)" :src="msg.avatarConfig.imgSrc" class="fallback-avatar" alt="avatar"
                    @error="avatarOnError"
                    @load="avatarOnLoad"
                  />
                  <div class="avatar-fallback">{{ (msg.avatarConfig && msg.avatarConfig.name) ? msg.avatarConfig.name.charAt(0) : '仙' }}</div>
                  <div class="bubble-content"><McMarkdownCard :content="msg.content" /></div>
                </div>
              </McBubble>
            </template>
            <div ref="messageEnd" class="h-0"></div>
          </div>
        </div>

        <aside class="sidebar">
          <div class="status-card">
            <div class="status-header">角色信息</div>
            <div class="status-row"><strong>境界</strong><span>{{ characterState.level }}</span></div>
            <div class="status-row"><strong>灵力</strong>
              <div class="progress"><div class="bar" :style="{ width: (characterState.spirit / characterState.maxSpirit * 100) + '%' }"></div></div>
              <small>{{ characterState.spirit }} / {{ characterState.maxSpirit }}</small>
            </div>
            <div class="status-row"><strong>气血</strong>
              <div class="progress"><div class="bar hp" :style="{ width: (characterState.hp / 100 * 100) + '%' }"></div></div>
              <small>{{ characterState.hp }} / 100</small>
            </div>
            <div class="status-actions">
                <button class="mini-btn" @click="newGame">重新悟道</button>
                <button class="mini-btn ghost" @click="messages.push({ from: 'model', content: '**日志**: 保存了当前进度。', loading:false })">保存进度</button>
                <button class="mini-btn" @click="onUploadClick">上传功法/灵植</button>
            </div>
          </div>
        </aside>
      </McLayoutContent>

      <!-- 隐藏的文件输入，用于上传功法或灵植图片 -->
      <input ref="fileInput" type="file" accept="*/*" style="display:none" @change="handleFileChange" />

      <!-- 底部输入与快捷提示（游戏指令） -->
      <div class="shortcut-panel">
        <McPrompt 
          :list="currentPrompts"
          :direction="'horizontal'"
          class="prompt-area"
          @itemClick="handleSend($event.label)"
        />
        <McLayoutSender>
          <McInput 
            v-model="inputValue" 
            :placeholder="inputPlaceholder" 
            @submit="handleSend"
          >
            <template #extra>
              <div class="input-foot-wrapper">
                <span class="input-foot-info">当前游戏回合: {{ gameTurn }}</span>
                    <!-- API Key / Base URL / Model 设置用于开发者测试 -->
                    <input class="apikey" v-model="apiKey" placeholder="API Key (Optional)" />
                    <input class="apikey" v-model="baseUrl" placeholder="Base URL (模型 API，例如 https://api.chatanywhere.tech)" />
                    <input class="apikey" v-model="modelName" placeholder="Model (e.g. gpt-4o-mini)" />
              </div>
            </template>
          </McInput>
        </McLayoutSender>
      </div>
    </McLayout>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, nextTick, computed } from 'vue';
// import { McLayout, McHeader, McBubble, McInput, McLayoutContent, McPrompt, McMarkdownCard, McLayoutSender } from '@matechat/core';

// --- 配置常量 ---
const appTitle = 'AI 修仙模拟器 (天道终端)';
const inputPlaceholder = '请输入您的修仙指令（如：闭关三年，炼制筑基丹）...';
const logoUrl = '/avatars/logo.svg'; // 使用本地 logo，避免外部占位图加载失败
const userAvatar = { name: 'Player', imgSrc: '/avatars/user.svg' };
const modelAvatar = { name: '天道', imgSrc: '/avatars/model.svg' };

// --- 响应式状态：游戏核心 ---
interface CharacterState {
  level: string;
  spirit: number;
  maxSpirit: number;
  hp: number;
  turn: number;
}
const inputValue = ref('');
const messages = ref<any[]>([]);
const gameTurn = ref(1);
const messageEnd = ref<HTMLElement | null>(null);

const characterState = ref<CharacterState>({
  level: '凡人',
  spirit: 0,
  maxSpirit: 100,
  hp: 100,
  turn: 1,
});

// logo 加载状态与回退
const logoError = ref(false);
const onLogoError = () => { logoError.value = true; };
const onLogoLoad = (_e: Event) => { /* 成功加载，不需要特殊处理 */ };

// 头像加载/错误处理
const avatarOnError = (e: Event) => {
  const img = e.target as HTMLImageElement | null;
  if (img) img.style.display = 'none';
};
const avatarOnLoad = (e: Event) => {
  const img = e.target as HTMLImageElement | null;
  if (!img) return;
  const next = img.nextElementSibling as HTMLElement | null;
  if (next) next.style.display = 'none';
};

// 文件上传相关引用
const fileInput = ref<HTMLInputElement | null>(null);

// 点击上传按钮，触发隐藏文件输入
const onUploadClick = () => {
  fileInput.value?.click();
};

// 处理文件选择
const handleFileChange = async (ev: Event) => {
  const input = ev.target as HTMLInputElement;
  const file = input.files?.[0];
  if (!file) return;

  // 将上传作为一个用户消息发出并创建模型占位
  messages.value.push({ from: 'user', content: `上传文件：${file.name}`, avatarConfig: userAvatar });
  messages.value.push({ from: 'model', content: '正在解析上传内容...', loading: true, avatarConfig: modelAvatar, id: '' });
  const modelIndex = messages.value.length - 1;

  // 交给处理函数（本地 mock 或远端）
  await processUploadedFile(file, modelIndex, { ...characterState.value });
  // 清空 file input 值，方便重复上传相同文件
  input.value = '';
};

// 处理上传文件：本地 mock 解析或发送到远端（如果配置了 apiKey）
const processUploadedFile = async (file: File, modelIndex: number, currentState: CharacterState) => {
  const used_api_key = apiKey.value && apiKey.value.trim() ? apiKey.value.trim() : DEFAULT_API_KEY;
  const used_base = baseUrl.value && baseUrl.value.trim() ? baseUrl.value.trim() : baseUrl.value;

  // 读取文件为 base64（用于远程发送或本地展示）
  const readFileAsDataUrl = (f: File) => new Promise<string>((resolve, reject) => {
    const fr = new FileReader();
    fr.onload = () => resolve(String(fr.result || ''));
    fr.onerror = (e) => reject(e);
    fr.readAsDataURL(f);
  });

  try {
    if (!used_api_key) {
      // 本地 mock：简单识别图片与文本
      const isImage = file.type.startsWith('image/');
      const dataUrl = await readFileAsDataUrl(file);
      let narrative = '';
      let spiritGain = 0;
      if (isImage) {
        spiritGain = Math.floor(Math.random() * 40) + 10;
        characterState.value.spirit = Math.min(characterState.value.maxSpirit, characterState.value.spirit + spiritGain);
        narrative = `你上传了一张灵植/异宝图片 (${file.name})。天道感应到此物具有灵气，增加了 ${spiritGain} 点灵力。`;
      } else {
        spiritGain = Math.floor(Math.random() * 20) + 5;
        characterState.value.spirit = Math.min(characterState.value.maxSpirit, characterState.value.spirit + spiritGain);
        narrative = `你上传了功法文档 (${file.name})。天道解析了其中的法诀，获得了 ${spiritGain} 点灵力启发。`;
      }


      // 构造回复并使用纯文本状态更新（不使用表格）
      let content = narrative + '\n\n' + formatStateUpdate(currentState, characterState.value, spiritGain) + '\n';
      content = normalizeMarkdownSpacing(content);

      messages.value[modelIndex].content = '';
      messages.value[modelIndex].loading = true;
      const chunks = content.match(/.{1,120}/g) || [content];
      for (const ch of chunks) {
        await new Promise((r) => setTimeout(r, 120));
        messages.value[modelIndex].content += ch;
        await scrollToBottom();
      }
      messages.value[modelIndex].loading = false;
      return;
    }

    // 如果有 apiKey，直接把文件信息发送给大模型的 Chat Completions 接口，避免调用自定义后端 /upload
    const dataUrl = await readFileAsDataUrl(file);
    const chatUrl = used_base.replace(/\/+$/,'') + '/chat/completions';
    // 构建 system 提示让模型扮演文件解析器
    const systemMsg = `You are a game assistant. The user uploaded a file. Analyze the file content (name: ${file.name}, type: ${file.type}) and respond with narrative and, if applicable, a JSON field 'new_state' describing role state updates. Return human-readable narrative first.`;
    const userMsg = `FILE_META: name=${file.name}; type=${file.type}; data_url_length=${dataUrl.length}; Please analyze the file and return narrative and optional new_state.`;

    const resp = await fetch(chatUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${used_api_key}` },
      body: JSON.stringify({ model: modelName.value, messages: [{ role: 'system', content: systemMsg }, { role: 'user', content: userMsg }], stream: false }),
    });
    if (!resp.ok) {
      const t = await resp.text();
      messages.value[modelIndex].content = `[Upload/Error] ${t}`;
      messages.value[modelIndex].loading = false;
      return;
    }

    const ct = resp.headers.get('content-type') || '';
    if (ct.includes('application/json')) {
      const data = await resp.json();
      // If backend/model returns structured new_state, apply it
      if (data && data.new_state && typeof data.new_state === 'object') {
        try { characterState.value = { ...characterState.value, ...data.new_state } as CharacterState; } catch (e) {}
      }
      let full = data.choices?.[0]?.message?.content || data.result || data.message || JSON.stringify(data);
      full = repairMarkdownTables(normalizeMarkdownSpacing(full));
      messages.value[modelIndex].content = '';
      messages.value[modelIndex].loading = true;
      const chunks = full.match(/.{1,120}/g) || [full];
      for (const ch of chunks) {
        await new Promise((r) => setTimeout(r, 120));
        messages.value[modelIndex].content += ch;
        await scrollToBottom();
      }
      messages.value[modelIndex].loading = false;
    } else {
      const text = await resp.text();
      const final = repairMarkdownTables(normalizeMarkdownSpacing(text));
      messages.value[modelIndex].content = '';
      messages.value[modelIndex].loading = true;
      const chunks = final.match(/.{1,120}/g) || [final];
      for (const ch of chunks) {
        await new Promise((r) => setTimeout(r, 120));
        messages.value[modelIndex].content += ch;
        await scrollToBottom();
      }
      messages.value[modelIndex].loading = false;
    }
  } catch (e) {
    console.error(e);
    messages.value[modelIndex].content = '[Upload failed] ' + String(e);
    messages.value[modelIndex].loading = false;
  }
};

// 远程调用配置（简化，仅用于演示 NL2UI 的集成点）
// 默认指向通用模型代理/服务（可以在 UI 中覆盖）
const DEFAULT_API_KEY = "";
const DEFAULT_BASE_URL = "https://api.chatanywhere.tech";
const apiKey = ref('');
// 默认指向大模型提供方或代理（可在界面中填写完整 URL）
const baseUrl = ref(DEFAULT_BASE_URL);
const modelName = ref('gpt-4o-mini');

// 快捷指令（McPrompt 的数据源）
const basePrompts = [
  { label: '闭关修炼', value: 'cultivate' },
  { label: '寻求突破', value: 'breakthrough' },
  { label: '炼制丹药', value: 'alchemy' },
];
const currentPrompts = computed(() => {
  // 根据当前境界动态调整指令
  if (characterState.value.level === '筑基期') {
    return [{ label: '探索秘境', value: 'explore' }, ...basePrompts];
  }
  return basePrompts;
});

// --- 核心方法 ---

// 滚动到底部
const scrollToBottom = () => {
  nextTick(() => {
    try {
      const container = document.querySelector('.content-container') as HTMLElement | null;
      if (container) {
        container.scrollTop = container.scrollHeight;
        return;
      }
    } catch (e) {
      // ignore scroll errors
    }
  });
};

// 模拟 AI 游戏引擎的响应逻辑
const mockGameEngineResponse = (command: string, state: CharacterState) => {
  let narrative = '';
  let newState = { ...state };
  let scoreChange = 0;
  
  if (command.includes('闭关')) {
    const gained = Math.floor(Math.random() * 50) + 10;
    newState.spirit = Math.min(newState.maxSpirit, state.spirit + gained);
    narrative = `你于洞府中闭关三日，感应天地灵气。你成功吸收了 ${gained} 点灵力。`;
    scoreChange = gained;
  } else if (command.includes('突破')) {
    if (state.spirit >= state.maxSpirit) {
      newState.level = '筑基期';
      newState.maxSpirit = 2000;
      newState.spirit = 100;
      narrative = `【恭喜！】天道震颤，你成功领悟天机，破入 ${newState.level}！灵力上限提升。`;
    } else {
      narrative = `你心急之下强行突破，灵力反噬，差点走火入魔。突破失败，请积蓄灵力再试。`;
      newState.spirit = Math.max(0, state.spirit - 50);
    }
    scoreChange = 50;
  } else {
    narrative = `你输入了一个天道无法识别的指令。请重新输入。`;
  }

  newState.turn += 1;
  return { narrative, new_state: newState, score_change: scoreChange };
};

// Markdown 规整：确保表格、分隔线和标题周围有空行，避免被渲染器吞并为单行
const normalizeMarkdownSpacing = (t: string) => {
  if (!t) return t;
  // normalize line endings
  t = t.replace(/\r\n/g, '\n');
  // collapse excessive blank lines to two
  t = t.replace(/\n{3,}/g, '\n\n');
  // Ensure there's at least two line breaks before and after a horizontal rule
  t = t.replace(/([^\n])\n?---/g, '$1\n\n---');
  t = t.replace(/---\n?([^\n])/g, '---\n\n$1');
  // Ensure table rows start on their own paragraph
  t = t.replace(/([^\n])\n(\|\s*:?-)/g, '$1\n\n$2');
    // If a table starts without a blank line before, add one (simpler pattern)
    t = t.replace(/([^\n])\n(\|\s*\S)/g, '$1\n\n$2');
  // Ensure markdown headings or bold blocks like **[...]** are separated
  t = t.replace(/([^\n])\n(\*\*)/g, '$1\n\n$2');
  // Final collapse of excessive blank lines
  t = t.replace(/\n{3,}/g, '\n\n');
  // Note: do not remove table separator lines (e.g. "| --- | --- |")
  // because that breaks Markdown table rendering. Keep separators intact.
  return t;
};

// 将 Markdown 表格转换为更友好的纯文本块，便于在 UI 中稳定显示
const convertTableToText = (text: string) => {
  if (!text) return text;
  const lines = text.split(/\r?\n/);
  let outLines: string[] = [];
  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    // detect table header (contains | and next line separator like :---)
    if (/^\s*\|.*\|\s*$/.test(line) && i + 1 < lines.length && /^\s*\|?\s*:?-{1,}/.test(lines[i + 1])) {
      // header columns
      // skip header and separator
      i += 2;
      // parse table rows
      const rows: string[] = [];
      while (i < lines.length && /^\s*\|/.test(lines[i])) {
        // split preserving empty cells, then trim
        const rawCols = lines[i].split('|').map(s => s.trim());
        // filter out purely separator rows or rows with insufficient content
        const nonEmpty = rawCols.filter(c => c && !/^-+$/.test(c));
        if (nonEmpty.length >= 2) {
          const key = rawCols[0] || nonEmpty[0];
          const rest = rawCols.slice(1).filter(c => c && !/^-+$/.test(c)).join(' | ');
          // skip if key looks like a separator
          if (!/^[-\s]*$/.test(key)) rows.push(`${key}: ${rest}`);
        }
        i++;
      }
      if (rows.length) {
        outLines.push('---');
        outLines.push('【角色状态更新】');
        outLines.push(...rows);
        outLines.push('');
      }
      continue;
    }
    outLines.push(line);
    i++;
  }
  // final cleanup: remove stray lines that are only pipes or dashes
  outLines = outLines.filter(l => !/^\s*[|\-\s]{1,}\s*$/.test(l));
  // collapse multiple blank lines
  return outLines.join('\n').replace(/\n{3,}/g, '\n\n');
};

// 修复常见的被破坏的 Markdown 表格（填补分隔行、删除空行与杂散符号）
const repairMarkdownTables = (text: string) => {
  if (!text) return text;
  const lines = text.split(/\r?\n/);
  const out: string[] = [];
  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    // detect probable table header line (contains at least one pipe and non-empty cells)
    if (/\|/.test(line) && /\S/.test(line.replace(/\|/g, ''))) {
      // collect table block
      const block: string[] = [];
      let j = i;
      while (j < lines.length && (/^\s*\|/.test(lines[j]) || /^\s*$/.test(lines[j]) || /^\s*-+\s*$/.test(lines[j]) || /^\s*\|-/.test(lines[j]) )) {
        block.push(lines[j]);
        j++;
      }
      // normalize block: remove rows that are only pipes or only dashes or empty
      const filtered = block.map(l => l.trim()).filter(l => l !== '' && !/^\|?[-|\s]+\|?$/.test(l));
      if (filtered.length === 0) {
        // nothing meaningful, push original first line and continue
        out.push(line);
        i++;
        continue;
      }
      // ensure header is first
      const header = filtered[0];
      // determine columns from header
      const cols = header.split('|').length - 1;
      const sepCols = new Array(Math.max(1, cols)).fill(' --- ').join('|');
      const sepLine = '|' + sepCols + '|';
      // build rows from remaining filtered lines (skip header)
      const rows = filtered.slice(1).map(r => {
        let s = r;
        // ensure leading/trailing pipe
        if (!/^\s*\|/.test(s)) s = '|' + s;
        if (!/\|\s*$/.test(s)) s = s + '|';
        return s.replace(/\s*\|\s*/g, ' | ').replace(/\s+$/,'');
      });
      // output normalized table: header, separator, rows
      out.push(header);
      out.push(sepLine);
      out.push(...rows);
      i = j;
      continue;
    }
    out.push(line);
    i++;
  }
  return out.join('\n');
};

// 将结构化角色更新格式化为纯文本块（不使用 Markdown 表格）
const formatStateUpdate = (prev: CharacterState, next: CharacterState, scoreChange: number) => {
  const lines: string[] = [];
  lines.push('---');
  lines.push('【角色状态更新】');
  lines.push(`境界: ${prev.level} -> ${next.level}`);
  lines.push(`灵力: ${next.spirit}/${next.maxSpirit} (变化: ${scoreChange > 0 ? '+' + scoreChange : scoreChange})`);
  lines.push('');
  return lines.join('\n');
};

// 核心逻辑：MateChat 的智能交互逻辑 (fetchDataForCultivation)
const fetchDataForCultivation = async (command: string, modelIndex: number, currentState: CharacterState) => {
  if (modelIndex == null || modelIndex < 0) return;

  // 优先使用远端游戏引擎（当提供 apiKey 时），否则回退到本地 mock
  const used_api_key = apiKey.value && apiKey.value.trim() ? apiKey.value.trim() : DEFAULT_API_KEY;
  const used_base = baseUrl.value && baseUrl.value.trim() ? baseUrl.value.trim() : baseUrl.value;

  if (!used_api_key) {
    // 本地模拟行为（保持原有逻辑）
    await new Promise(resolve => setTimeout(resolve, 600));
    const { narrative, new_state, score_change } = mockGameEngineResponse(command, currentState);
    characterState.value = new_state;

    let content = narrative;
    // 确保 narrative 以至少两个换行结束，避免和后续分割线连成一行
    if (!content.endsWith('\n')) content += '\n\n';
    else if (!content.endsWith('\n\n')) content += '\n';
    // 使用纯文本的状态更新格式（不使用 Markdown 表格），并规整空行
    content += '\n\n' + formatStateUpdate(currentState, new_state, score_change) + '\n';
    content = normalizeMarkdownSpacing(content);
    messages.value[modelIndex].content = '';
    messages.value[modelIndex].loading = true;
    const chunks = content.match(/.{1,120}/g) || [content];
    for (const ch of chunks) {
      await new Promise(r => setTimeout(r, 120));
      messages.value[modelIndex].content += ch;
      await scrollToBottom();
    }
    messages.value[modelIndex].loading = false;
    gameTurn.value = new_state.turn;
    return;
  }

  // 使用远端游戏引擎：构建带历史的 messages（含 system 提示）并支持流式解析与非流式分片展示
  try {
    // Align remote endpoint with Chat.vue example: call /chat/completions by default
    const url = used_base.replace(/\/+$/,'') + '/chat/completions';

    // 构建历史对话（限制长度）
    const maxHistory = 20;
    const startIdx = Math.max(0, messages.value.length - maxHistory);
    const historyMessages: Array<{role:string, content:string}> = [];
    historyMessages.push({ role: 'system', content: 'You are a cultivation game engine. Provide narrative, update character state, and return structured status. Use Markdown for tables and emphasis.' });
    for (let i = startIdx; i < messages.value.length; i++) {
      const m = messages.value[i];
      if (!m || typeof m.content !== 'string') continue;
      const role = m.from === 'user' ? 'user' : 'assistant';
      historyMessages.push({ role, content: m.content });
    }
    historyMessages.push({ role: 'user', content: command });

    const resp = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'text/event-stream, application/json, */*',
        'Authorization': `Bearer ${used_api_key}`,
      },
      body: JSON.stringify({ model: modelName.value, messages: historyMessages, stream: true }),
    });

    if (!resp.ok) {
      const t = await resp.text();
      messages.value[modelIndex].content = `[Error] ${t}`;
      messages.value[modelIndex].loading = false;
      return;
    }

    const ct = resp.headers.get('content-type') || '';
    if (ct.includes('application/json')) {
      const data = await resp.json();
      // If backend returns structured new_state, use it to update characterState
      if (data && data.new_state && typeof data.new_state === 'object') {
        try {
          characterState.value = { ...characterState.value, ...data.new_state } as CharacterState;
          if ((data.new_state as any).turn) gameTurn.value = (data.new_state as any).turn;
        } catch (e) {
          // ignore malformed state
        }
      }
      let full = data.choices?.[0]?.message?.content || data.text || data.result || data.message || JSON.stringify(data);
      full = repairMarkdownTables(normalizeMarkdownSpacing(full));
      messages.value[modelIndex].content = '';
      messages.value[modelIndex].loading = true;
      const chunksFull = full.match(/.{1,120}/g) || [full];
      for (const ch of chunksFull) {
        await new Promise((r) => setTimeout(r, 120));
        messages.value[modelIndex].content += ch;
        await scrollToBottom();
      }
      messages.value[modelIndex].loading = false;
    } else if (resp.body) {
      messages.value[modelIndex].content = '';
      messages.value[modelIndex].loading = true;
      const reader = resp.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split(/\r?\n/);
        buffer = lines.pop() || '';
        for (const rawLine of lines) {
          const line = rawLine.trim();
          if (!line) continue;
          const dataLine = line.startsWith('data:') ? line.replace(/^data:\s*/, '') : line;
          if (dataLine === '[DONE]') continue;
          try {
            const obj = JSON.parse(dataLine);
            const content = obj.choices?.[0]?.delta?.content ?? obj.choices?.[0]?.message?.content ?? obj.choices?.[0]?.text ?? '';
            if (content) {
              messages.value[modelIndex].content += content;
              await scrollToBottom();
            }
          } catch (e) {
            messages.value[modelIndex].content += dataLine;
            await scrollToBottom();
          }
        }
      }
      if (buffer) {
        const maybe = buffer.trim();
        if (maybe && maybe !== '[DONE]') {
          try {
            const obj = JSON.parse(maybe);
            const content = obj.choices?.[0]?.delta?.content ?? obj.choices?.[0]?.message?.content ?? obj.choices?.[0]?.text ?? '';
            if (content) messages.value[modelIndex].content += content;
          } catch (e) {
            messages.value[modelIndex].content += maybe;
          }
        }
      }
      // 在流结束后做一次 Markdown 空行规整（保留原始表格渲染）
      try {
        messages.value[modelIndex].content = repairMarkdownTables(normalizeMarkdownSpacing(messages.value[modelIndex].content || ''));
      } catch (e) {
        // ignore
      }
    } else {
      const text = await resp.text();
      const final = repairMarkdownTables(normalizeMarkdownSpacing(text));
      messages.value[modelIndex].content = '';
      messages.value[modelIndex].loading = true;
      const chunksText = final.match(/.{1,120}/g) || [final];
      for (const ch of chunksText) {
        await new Promise((r) => setTimeout(r, 120));
        messages.value[modelIndex].content += ch;
        await scrollToBottom();
      }
      messages.value[modelIndex].loading = false;
    }

    // 尝试从最后返回内容中解析并更新角色状态（如果后端返回了结构化 state，简单尝试解析 Markdown 表格）
    // 注意：更可靠的方法是让后端返回 JSON 字段（例如 `new_state`），这里为演示做最小尝试解析。
    try {
      const last = messages.value[modelIndex].content || '';
      // 简单正则匹配灵力和境界更新（如果后端用 Markdown 表格返回）
      const spiritMatch = last.match(/\| 灵力 \|\s*([0-9]+)\/([0-9]+) \|/);
      if (spiritMatch) {
        const sp = parseInt(spiritMatch[1], 10);
        const maxSp = parseInt(spiritMatch[2], 10);
        characterState.value.spirit = sp;
        characterState.value.maxSpirit = maxSp;
      }
      const levelMatch = last.match(/\| 境界 \| .* -> \*\*(.*?)\*\*/);
      if (levelMatch) {
        characterState.value.level = levelMatch[1];
      }
    } catch (e) {
      // ignore parsing errors
    }

  } catch (err) {
    console.error(err);
    // 如果用户配置了 apiKey 或尝试远端，但请求失败（网络/CORS 等），回退到本地 mock 行为
    try {
      const used_api_key = apiKey.value && apiKey.value.trim() ? apiKey.value.trim() : DEFAULT_API_KEY;
      if (used_api_key) {
        messages.value[modelIndex].content = '远端请求失败，已回退到本地模拟。';
        // 使用本地模拟引擎产生内容并更新角色状态
        const { narrative, new_state, score_change } = mockGameEngineResponse(command, currentState);
        characterState.value = new_state;
        let content = narrative + '\n\n' + formatStateUpdate(currentState, new_state, score_change) + '\n';
        content = normalizeMarkdownSpacing(content);
        messages.value[modelIndex].content = '';
        messages.value[modelIndex].loading = true;
        const chunks = content.match(/.{1,120}/g) || [content];
        for (const ch of chunks) {
          await new Promise((r) => setTimeout(r, 120));
          messages.value[modelIndex].content += ch;
          await scrollToBottom();
        }
        messages.value[modelIndex].loading = false;
        gameTurn.value = new_state.turn;
        return;
      }
    } catch (e) {
      console.error('Fallback mock failed', e);
    }

    messages.value[modelIndex].content = '[Request failed] ' + String(err);
  } finally {
    messages.value[modelIndex].loading = false;
    await scrollToBottom();
  }
};

// 处理用户发送消息
const handleSend = (evt: string | Event | any) => {
  // 确保输入是字符串
  let command = '';
  if (typeof evt === 'string') command = evt.trim();
  else if (evt && typeof evt === 'object' && typeof evt.label === 'string') command = evt.label.trim();
  else command = inputValue.value.trim();

  if (!command) return;

  inputValue.value = '';

  // push user message
  messages.value.push({ from: 'user', content: command, avatarConfig: userAvatar });

  // push placeholder for model reply and capture index
  messages.value.push({ from: 'model', content: '天道推演中...', loading: true, avatarConfig: modelAvatar, id: '' });
  const modelIndex = messages.value.length - 1;

  // fetch data (streaming or simulated)
  fetchDataForCultivation(command, modelIndex, { ...characterState.value });
};

// 新建游戏
const newGame = () => {
  messages.value = [];
  gameTurn.value = 1;
  characterState.value = {
    level: '凡人',
    spirit: 0,
    maxSpirit: 100,
    hp: 100,
    turn: 1,
  };
  messages.value.push({
    from: 'model',
    content: `**【天道初始】** 欢迎来到修仙世界。你已成为一名凡人。你的目标是修炼至大乘期。

请选择你的指令：闭关修炼以积攒灵力，或寻求突破。`,
    loading: false,
  });
  scrollToBottom();
};

onMounted(() => {
  newGame();
});
</script>

<style scoped>
.cultivation-app {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: radial-gradient(circle at top left, #5e7ce0, #001f3f 70%); /* 深蓝星空背景 */
}
.container {
  width: 95%;
  max-width: 900px;
  height: calc(100vh - 40px);
  max-height: 900px;
  padding: 0;
  gap: 0;
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid #e6edf3;
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.content-container {
  flex-grow: 1;
  overflow-y: auto;
  padding: 16px;
  background-color: #f7f9fa; /* 浅色背景保持文字可读 */
}
.operations { 
  display:flex; 
  align-items:center; 
  gap:16px;
  font-weight: 600;
  color: #fff;
}
.icon-emoji { font-size:18px; display:inline-block; margin-right:6px }
.ml-4 { margin-left: 12px }
.app-logo { width:28px; height:28px; border-radius:6px; object-fit:cover; margin-right:8px }
.status-text {
  background: rgba(255, 255, 255, 0.2);
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 14px;
}
.mini-btn { 
  background:#fff; 
  color:#5e7ce0; 
  border:0; 
  padding:6px 12px; 
  border-radius:8px; 
  cursor:pointer;
  font-weight: 600;
}
.mini-btn:hover {
  background: #f0f0f0;
}
.shortcut-panel {
  padding: 12px 20px 0;
  background: #fff;
  border-top: 1px solid #eef2f6;
  display: flex;
  flex-direction: column;
}
.prompt-area {
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px dashed #eef2f6;
}
.input-foot-wrapper { 
  display:flex; 
  justify-content:space-between; 
  align-items:center; 
  width:100%; 
}
.input-foot-info {
  font-size: 13px;
  color: #71757f;
}
.apikey { 
  padding:4px 8px; 
  border-radius:6px; 
  border:1px dashed #c7d2fe; 
  width:180px; 
  font-size: 12px;
}

/* 新的布局样式 */
.content-container {
  display: flex;
  gap: 16px;
  padding: 20px;
}
.content-main { flex: 1; display: flex; flex-direction: column; }
.messages-wrap { display:flex; flex-direction:column; gap:12px; overflow:auto; max-height: calc(100vh - 260px); }
.sidebar { width: 260px; }
.status-card { background: linear-gradient(180deg,#fff,#fbfdff); border-radius:10px; padding:14px; box-shadow:0 6px 18px rgba(16,24,40,0.06); border:1px solid #eef6ff }
.status-header { font-weight:700; margin-bottom:8px; }
.status-row { margin-bottom:10px; display:flex; flex-direction:column }
.status-row strong { font-size:13px; color:#333 }
.progress { width:100%; height:10px; background:#eef6ff; border-radius:999px; margin:6px 0 }
.progress .bar { height:100%; background: linear-gradient(90deg,#3ac295,#5e7ce0); border-radius:999px }
.progress .bar.hp { background: linear-gradient(90deg,#ff7a7a,#ffb199) }
.status-actions { display:flex; gap:8px; margin-top:8px }
.mini-btn.ghost { background:transparent; color:#5e7ce0; border:1px solid rgba(94,124,224,0.12) }

/* 响应式 */
@media (max-width: 900px) {
  .container { width: 95%; }
  .sidebar { display:none }
}

/* 覆盖 MateChat Header 样式以配合游戏主题 */
.container :deep(.mc-header) {
  background: #4f46e5;
  color: #fff;
  padding: 12px 20px;
}
.container :deep(.mc-header-title) {
  font-weight: 700;
  font-size: 18px;
}

/* message avatar & layout fallback */
.bubble-inner { display:flex; align-items:flex-start; gap:10px }
.fallback-avatar { width:36px; height:36px; border-radius:8px; object-fit:cover; background:#f3f4f6 }
.avatar-fallback { width:36px; height:36px; border-radius:8px; display:flex; align-items:center; justify-content:center; background:#e6eef8; color:#324a9b; font-weight:700 }
.bubble-content { flex:1 }
</style>